﻿//Copyright (c) Microsoft Corporation.  All rights reserved.

using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using Microsoft.Ted.Wacel.Base;
using System.Reflection;

namespace Microsoft.Ted.Wacel.TableStorage
{
    public class AutoDynamicTableEntity<T> : ITableEntity where T : new()
    {
        public string ETag { get; set; }
        public string PartitionKey { get; set; }
        public string RowKey { get; set; }
        public DateTimeOffset Timestamp { get; set; }

        private string mPartitionField { get; set; }
        private string mRowField { get; set; }

        public T POCO { get; private set; }
        private IDictionary<string, EntityProperty> mProperties = new Dictionary<string, EntityProperty>();

        public AutoDynamicTableEntity()
        {
            //This contructor is rquired by TableQuery
        }
        public AutoDynamicTableEntity(T poco, string paritionKey, string rowKey, string partitionField, string rowField)
        {
            PartitionKey = paritionKey;
            RowKey = rowKey;
            POCO = poco;
            mPartitionField = partitionField;
            mRowField = rowField;

            refreshProperties();
        }
        private void updatePropert(string name, EntityProperty value)
        {
            if (mProperties.ContainsKey(name))
                mProperties[name] = value;
            else
                mProperties.Add(name, value);
        }
        private void refreshProperties()
        {
            Type type = typeof(T);
            var properties = type.GetProperties();

            //TODO: support nullable properties? Type safety checks?
            lock (mProperties)
            {
                foreach (var property in properties)
                {
                    if (property.Name == mPartitionField || property.Name == mRowField)
                    {
                        if (property.Name == mPartitionField)
                            PartitionKey = property.GetValue(POCO).ToString();
                        if (property.Name == mRowField)
                            RowKey = property.GetValue(POCO).ToString();
                    }
                    else
                    {
                        switch (property.PropertyType.Name)
                        {
                            case "Int32":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as Int32?));
                                break;
                            case "String":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as string));
                                break;
                            case "Boolean":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as bool?));
                                break;
                            case "DateTime":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as DateTime?));
                                break;
                            case "Byte[]":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as byte[]));
                                break;
                            case "Double":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as double?));
                                break;
                            case "DateTimeOffset":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as DateTimeOffset?));
                                break;
                            case "Int64":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as long?));
                                break;
                            case "Guid":
                                updatePropert(property.Name, new EntityProperty(property.GetValue(POCO) as Guid?));
                                break;
                            default:
                                updatePropert(property.Name, new EntityProperty(JsonSerializer.Serialize(property.PropertyType, property.GetValue(POCO))));
                                break;
                        }
                    }
                }
            }
        }

       
        public void ReadEntity(IDictionary<string, EntityProperty> properties, Microsoft.WindowsAzure.Storage.OperationContext operationContext)
        {
            Type type = typeof(T);
            if (POCO == null)
                POCO = new T();
            lock (POCO)
            {
                foreach (var key in properties.Keys)
                {
                    var property = type.GetProperty(key);
                    if (property != null)
                    {
                        switch (properties[key].PropertyType)
                        {
                            case EdmType.Binary:
                                if (property.PropertyType.Name == "Byte[]")
                                    property.SetValue(POCO, properties[key].BinaryValue);
                                else
                                    property.SetValue(POCO, JsonSerializer.Deserialize(property.PropertyType, properties[key].BinaryValue));
                                break;
                            case EdmType.Boolean:
                                property.SetValue(POCO, properties[key].BooleanValue);
                                break;
                            case EdmType.Double:
                                property.SetValue(POCO, properties[key].DoubleValue);
                                break;
                            case EdmType.Guid:
                                property.SetValue(POCO, properties[key].GuidValue);
                                break;
                            case EdmType.Int32:
                                property.SetValue(POCO, properties[key].Int32Value);
                                break;
                            case EdmType.Int64:
                                property.SetValue(POCO, properties[key].Int64Value);
                                break;
                            case EdmType.String:
                                property.SetValue(POCO, properties[key].StringValue);
                                break;
                            case EdmType.DateTime:
                                property.SetValue(POCO, properties[key].DateTimeOffsetValue.Value.DateTime); //TODO: is this correct?
                                break;
                        }
                    }
                }
            }
        }

        public IDictionary<string, EntityProperty> WriteEntity(Microsoft.WindowsAzure.Storage.OperationContext operationContext)
        {
            refreshProperties(); //TODO: we need to do this because original POCO is mutable
            return mProperties;
        }
    }
}
